package com.cxx.purchasecharge.dal.auditlog;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.cxx.purchasecharge.core.model.LogTypeCode;
import com.cxx.purchasecharge.core.model.persistence.auditlog.Log;
import com.cxx.purchasecharge.core.model.persistence.auditlog.LogContent;
import com.cxx.purchasecharge.core.util.DateUtils;
import com.cxx.purchasecharge.core.util.ModelClassHelper;
import com.cxx.purchasecharge.dal.common.GenericDaoUtils;

@SuppressWarnings ("all")
public class LogUtil
{
    private static final Logger LOGGER = Logger.getLogger (LogUtil.class);
    private LogDao logDao;
    @PersistenceContext
    private EntityManager entityManager;

    public void logForInsert (Object obj, String userId)
    {
        LOGGER.debug ("object: " + obj + ", userId: " + userId);
        if (null != obj && StringUtils.isNotBlank (userId))
        {
            Log log = new Log ();
            log.setTableName (obj.getClass ().getSimpleName ());
            log.setType (LogTypeCode.INSERT);
            log.setDate (new Date ());
            log.setUserId (userId);
            log.setContents (buildLogContents (obj, log));

            try
            {
                logDao.save (log);
            }
            catch (Exception e)
            {
                LOGGER.warn ("Save log failed. " + e.getMessage (), e);
                LOGGER.warn (log);
            }
        }
        else
        {
            LOGGER.warn ("Can not write log for insert when target is null or user is null");
        }
    }

    public void logForUpdate (Object source, Object target, String userId)
    {
        LOGGER.debug ("source object: " + source + ", target object: " + target + ", userId: " + userId);
        if (null != source && null != target && StringUtils.isNotBlank (userId))
        {
            if (source.getClass ().getName ().equals (target.getClass ().getName ()))
            {
                Log log = new Log ();
                log.setTableName (source.getClass ().getSimpleName ());
                log.setType (LogTypeCode.UPDATE);
                log.setDate (new Date ());
                log.setUserId (userId);

                List <LogContent> logContents = new ArrayList <LogContent> ();
                List <Map <String, Object>> sourceFieldInfos = ModelClassHelper.getFieldsInfo (source);
                List <Map <String, Object>> targetFieldInfos = ModelClassHelper.getFieldsInfo (target);
                for (Iterator <Map <String, Object>> sourceIter = sourceFieldInfos.iterator (); sourceIter.hasNext ();)
                {
                    Map <String, Object> sourceMap = (Map <String, Object>) sourceIter.next ();
                    String fieldName = sourceMap.get (ModelClassHelper.FIELD_NAME).toString ();
                    Object fieldValue = sourceMap.get (ModelClassHelper.FIELD_VALUE);
                    for (Iterator <Map <String, Object>> targetIter = targetFieldInfos.iterator (); targetIter.hasNext ();)
                    {
                        Map <String, Object> targetMap = (Map <String, Object>) targetIter.next ();
                        if (fieldName.equals (targetMap.get (ModelClassHelper.FIELD_NAME).toString ())
                            && !checkEquals (fieldValue, targetMap.get (ModelClassHelper.FIELD_VALUE)))
                        {
                            LogContent logContent = new LogContent ();
                            logContent.setLog (log);
                            logContent.setField (fieldName);
                            logContent.setOldValue (convert (fieldValue));
                            logContent.setNewValue (convert (targetMap.get (ModelClassHelper.FIELD_VALUE)));
                            logContent.setComment (targetMap.get (ModelClassHelper.FIELD_TYPE).toString ());
                            logContents.add (logContent);
                            break;
                        }
                    }
                }
                log.setContents (logContents);

                try
                {
                    logDao.save (log);
                }
                catch (Exception e)
                {
                    LOGGER.warn ("Save log failed. " + e.getMessage (), e);
                    LOGGER.warn (log);
                }
            }
        }
        else
        {
            LOGGER.warn ("Can not write log for update when source is null or target is null or user is null");
        }
    }

    public void logForDelete (Object obj, String userId)
    {
        LOGGER.debug ("object: " + obj + ", userId: " + userId);
        if (null != obj && StringUtils.isNotBlank (userId))
        {
            Log log = new Log ();
            log.setTableName (obj.getClass ().getSimpleName ());
            log.setType (LogTypeCode.DELETE);
            log.setDate (new Date ());
            log.setUserId (userId);

            long id = (Long) GenericDaoUtils.getPrimaryKey (obj);
            Object target = getDBModel (obj.getClass (), id);
            log.setContents (buildLogContents (target, log));

            try
            {
                logDao.save (log);
            }
            catch (Exception e)
            {
                LOGGER.warn ("Save log failed. " + e.getMessage (), e);
            }
        }
        else
        {
            LOGGER.warn ("Can not write log for delete when target is null or user is null");
        }
    }

    public void logForLog (String userId, List <String> authorities, boolean isLogin)
    {
        LOGGER.debug ("userId: " + userId + ", authorities: " + authorities + ", isLogin: " + isLogin);
        if (StringUtils.isNotBlank (userId))
        {
            Log log = new Log ();
            log.setTableName ("User");
            log.setDate (new Date ());
            log.setUserId (userId);
            if (isLogin)
            {
                log.setType (LogTypeCode.LOGIN);
                log.setComment ("用户权限为" + authorities);
            }
            else
            {
                log.setType (LogTypeCode.LOGOUT);
            }

            try
            {
                logDao.save (log);
            }
            catch (Exception e)
            {
                LOGGER.warn ("Save log failed. " + e.getMessage (), e);
            }
        }
        else
        {
            LOGGER.warn ("Can not write log for login or logout when userId is null");
        }
    }

    private List <LogContent> buildLogContents (Object obj, Log log)
    {
        List <LogContent> logContents = new ArrayList <LogContent> ();
        List <Map <String, Object>> fieldInfos = ModelClassHelper.getFieldsInfo (obj);
        for (Iterator <Map <String, Object>> iter = fieldInfos.iterator (); iter.hasNext ();)
        {
            Map <String, Object> map = (Map <String, Object>) iter.next ();
            Object fieldValue = map.get (ModelClassHelper.FIELD_VALUE);
            Object fieldType = map.get (ModelClassHelper.FIELD_TYPE);
            Class clazz = ((Class) fieldType);
            if (null != fieldValue)
            {
                if (isLogFieldType (fieldType))
                {
                    if (isNeedLog (fieldValue))
                    {
                        LogContent logContent = new LogContent ();
                        logContent.setLog (log);
                        logContent.setField (map.get (ModelClassHelper.FIELD_NAME).toString ());
                        logContent.setNewValue (convert (fieldValue));
                        logContent.setComment (clazz.getSimpleName ());
                        logContents.add (logContent);
                    }
                }
                else if (isInternalObject (fieldType))
                {
                    logContents.addAll (buildLogContents (fieldValue, log));
                }
            }
        }

        return logContents;
    }

    private boolean isLogFieldType (Object fieldType)
    {
        if (null != fieldType)
        {
            Class fieldClazz = (Class) fieldType;
            String className = fieldClazz.getName ();
            return "int".equals (className) || "long".equals (className) || "short".equals (className)
                   || "byte".equals (className) || "float".equals (className) || "double".equals (className)
                   || "boolean".equals (className) || "java.lang.String".equals (className) || fieldClazz.isEnum ()
                   || "java.util.Date".equals (className);
        }
        return false;
    }

    private boolean isInternalObject (Object fieldType)
    {
        return null != fieldType
               && ((Class) fieldType).getName ().startsWith ("com.cxx.purchasecharge.core.model.persistence");
    }

    private boolean isNeedLog (Object fieldValue)
    {
        return null != fieldValue && StringUtils.isNotBlank (fieldValue.toString ())
               && !"0".equals (fieldValue.toString ()) && !"0.0".equals (fieldValue.toString ());
    }

    private boolean checkEquals (Object source, Object target)
    {
        if (null == source && null == target)
        {
            return true;
        }
        if (null != source && null != target && source.getClass ().getName ().equals (target.getClass ().getName ()))
        {
            if (source instanceof java.lang.Number || source instanceof java.lang.String
                || source instanceof java.lang.Enum)
            {
                return source.toString ().equals (target.toString ());
            }
            else if (source instanceof java.util.Collection)
            {
                // TBD
            }
            else if (source instanceof java.util.Map)
            {
                // TBD
            }
            else
            {
                return source.toString ().equals (target.toString ());
            }
        }
        if (null == source || null == target)
        {
            return false;
        }
        return false;
    }

    @SuppressWarnings ("all")
    public Object getDBModel (Class clazz, long id)
    {
        return entityManager.find (clazz, id);
    }

    private String convert (Object obj)
    {
        if (null != obj && obj instanceof Date)
        {
            return DateUtils.date2String ((Date) obj, DateUtils.DATE_TIME_PATTERN);
        }
        return null != obj ? obj.toString () : "";
    }

    public LogDao getLogDao ()
    {
        return logDao;
    }

    public void setLogDao (LogDao logDao)
    {
        this.logDao = logDao;
    }
}
